﻿using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;
using System.IO;

/// <summary>
/// 合并skin mesh，减少DrawCall
/// </summary>
public class SkinMeshCombineTool : MonoBehaviour
{

    [MenuItem("Tools/SkinMesh Combine")]
    public static void StartCombine()
    {
        if (Selection.activeGameObject != null)
        {
            var root = Selection.activeGameObject;
            if (!root.activeInHierarchy)
            {
                Debug.LogError("GameObject is Must Active in Hierarchy!!!");
                return;
            }

            PrefabAssetType prefabType = PrefabUtility.GetPrefabAssetType(root);
            if (prefabType != PrefabAssetType.NotAPrefab)
            {
                Debug.LogError("GameObject is a Prefab,Please unpack prefab in Hierarchy !!!");
                return;
            }

            List<CombineInstance> combineInstances = new List<CombineInstance>();
            List<Transform> bonelist = new List<Transform>();
            Transform[] transforms = root.GetComponentsInChildren<Transform>();
            List<Texture2D> textures = new List<Texture2D>();
            List<Vector2[]> uvList = new List<Vector2[]>();
            Matrix4x4 matrix = root.transform.worldToLocalMatrix;
            int width = 0;
            int height = 0;
            int uvCount = 0;

            var allSkineMeshList = root.GetComponentsInChildren<SkinnedMeshRenderer>();
            foreach(var smr in allSkineMeshList)
            {
                for(int i = 0; i < smr.sharedMesh.subMeshCount; i++)
                {
                    CombineInstance ci = new CombineInstance();
                    ci.mesh = smr.sharedMesh;
                    ci.mesh.triangles = smr.sharedMesh.triangles;
                    ci.subMeshIndex = i;
                    ci.transform = matrix * smr.transform.localToWorldMatrix;
                    combineInstances.Add(ci);
                }

                uvList.Add(smr.sharedMesh.uv);
                uvCount += smr.sharedMesh.uv.Length;

                if(smr.sharedMaterial.mainTexture != null)
                {
                    CheckTextureFormat(smr.sharedMaterial.mainTexture);
                    textures.Add(smr.sharedMaterial.mainTexture as Texture2D);
                    width += smr.sharedMaterial.mainTexture.width;
                    height += smr.sharedMaterial.mainTexture.height;
                }

                foreach(var bone in smr.bones)
                {
                    foreach(var t in transforms)
                    {
                        if (t.name != bone.name) continue;
                        bonelist.Add(t);
                        break;
                    }
                }
            }
            // 销毁所有部件
            Shader shader = allSkineMeshList[0].sharedMaterial.shader;
            allSkineMeshList = root.GetComponentsInChildren<SkinnedMeshRenderer>();
            foreach (var skin in allSkineMeshList)
            {
                if (skin != null)
                    DestroyImmediate(skin.gameObject);
            }

            SkinnedMeshRenderer tempRenderer = root.GetComponent<SkinnedMeshRenderer>();
            if(tempRenderer==null) tempRenderer = root.AddComponent<SkinnedMeshRenderer>();
            tempRenderer.sharedMesh = new Mesh();
            tempRenderer.sharedMesh.CombineMeshes(combineInstances.ToArray(), true, false);
            tempRenderer.bones = bonelist.ToArray();

            Texture2D skinMeshAtlas = new Texture2D(get2Pow(width), get2Pow(height), TextureFormat.ARGB32, false);
            Rect[] packingResult = skinMeshAtlas.PackTextures(textures.ToArray(), 0);
            Vector2[] atlasUVs = new Vector2[uvCount];

            // 因为将贴图都整合到了一张图片上，所以需要重新计算UV
            int j = 0;
            for (int i = 0; i < uvList.Count; i++)
            {
                foreach (Vector2 uv in uvList[i])
                {
                    atlasUVs[j].x = Mathf.Lerp(packingResult[i].xMin, packingResult[i].xMax, uv.x);
                    atlasUVs[j].y = Mathf.Lerp(packingResult[i].yMin, packingResult[i].yMax, uv.y);
                    j++;
                }
            }
            tempRenderer.sharedMaterial = new Material(shader);
            tempRenderer.sharedMaterial.mainTexture = skinMeshAtlas;
            tempRenderer.sharedMesh.uv = atlasUVs;
            tempRenderer.sharedMesh.uv2 = null;
            tempRenderer.sharedMesh.uv3 = null;
            tempRenderer.sharedMesh.uv4 = null;
            tempRenderer.sharedMesh.colors32 = null;

            tempRenderer.sharedMesh.UploadMeshData(true);
            CreatCombinePrefab(tempRenderer);

        }
    }

    /// <summary>
    /// 获取最接近输入值的2的N次方的数，最大不会超过1024，例如输入320会得到512
    /// </summary>
    private static int get2Pow(int into)
    {
        int outo = 1;
        for (int i = 0; i < 10; i++)
        {
            outo *= 2;
            if (outo > into)
            {
                break;
            }
        }

        return outo;
    }


    private static void CheckTextureFormat(Texture image)
    {
        string assetPath = AssetDatabase.GetAssetPath(image);
        TextureImporter textureImporter = AssetImporter.GetAtPath(assetPath) as TextureImporter;
        if (textureImporter != null)
        {
            bool flag = false;
            if (!textureImporter.isReadable)
            {
                textureImporter.isReadable = true;
                flag = true;
            }
            if (textureImporter.textureCompression != TextureImporterCompression.Uncompressed)
            {
                textureImporter.textureCompression = TextureImporterCompression.Uncompressed;
                flag = true;
            }

            if (textureImporter.mipmapEnabled)
            {
                textureImporter.mipmapEnabled = false;
                flag = true;
            }
            if (textureImporter.npotScale != TextureImporterNPOTScale.None)
            {
                textureImporter.npotScale = TextureImporterNPOTScale.None;
                flag = true;
            }
            //必须RBGA32
            var defaultformat = textureImporter.GetDefaultPlatformTextureSettings();
            if (defaultformat.format != TextureImporterFormat.RGBA32)
            {
                defaultformat.format = TextureImporterFormat.RGBA32;
                textureImporter.SetPlatformTextureSettings(defaultformat);
                flag = true;
            }

            if (flag)
            {
                textureImporter.SaveAndReimport();
            }
        }
    }



    private static void CreatCombinePrefab(SkinnedMeshRenderer tempRenderer)
    {
        string savePath = "Assets/MeshCombine";
        if (!Directory.Exists(savePath)) Directory.CreateDirectory(savePath);
        string filename = tempRenderer.gameObject.name.Trim();
        filename = filename.Replace("(Clone)", "");
        string parentPath = Path.Combine(savePath, filename);
        if (!Directory.Exists(parentPath)) Directory.CreateDirectory(parentPath);

        string pngPath = Path.Combine(parentPath, filename + ".png");
        string meshPath = Path.Combine(parentPath, filename + ".asset");
        string matPath = Path.Combine(parentPath, filename + ".mat");
        string prefabPath = Path.Combine(Application.dataPath, "MeshCombine/"+ filename, filename + ".prefab");

        Texture2D cur2d = tempRenderer.sharedMaterial.mainTexture as Texture2D;

        Texture2D fontatlas = new Texture2D(cur2d.width, cur2d.height, TextureFormat.ARGB32, false);
        fontatlas.name = filename + " Atlas";
        fontatlas.PackTextures(new Texture2D[] { cur2d }, 0);


        byte[] array4 = ImageConversion.EncodeToPNG(fontatlas);
        FileStream fileStream = File.OpenWrite(pngPath);
        fileStream.Write(array4, 0, array4.Length);
        fileStream.Close();
        AssetDatabase.CreateAsset(tempRenderer.sharedMesh, meshPath);
        AssetDatabase.CreateAsset(tempRenderer.sharedMaterial, matPath);
        PrefabUtility.SaveAsPrefabAssetAndConnect(tempRenderer.gameObject, prefabPath, InteractionMode.AutomatedAction);
        AssetDatabase.Refresh();
        var mat = AssetDatabase.LoadAssetAtPath<Material>(matPath);
        var texture = AssetDatabase.LoadAssetAtPath<Texture2D>(pngPath);
        mat.mainTexture = texture;
        TextureImporter textureImporter = AssetImporter.GetAtPath(pngPath) as TextureImporter;
        if (textureImporter != null)
        {
            if (textureImporter.mipmapEnabled)
            {
                textureImporter.maxTextureSize = Mathf.Max(cur2d.width, cur2d.height);
                textureImporter.mipmapEnabled = false;
                textureImporter.SaveAndReimport();

            }
        }
        AssetDatabase.Refresh();
    }
}
